'''
    This file is part of Ognom Keeper.

    Ognom Keeper is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Ognom Keeper is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Ognom Keeper.  If not, see <http://www.gnu.org/licenses/>.


    Copyright(C) 2009 Viktor Vestberg
'''

try: 
    
    from parts import *
    from utils import *
except ImportError, err:
    print str("couldn't load module. %s" % (err))

class Manager:
    """The Manager is used for administring handlers. It sends out signals to all added handlers.
        Things such as updating, stopping pausing. It can also exit the application"""
    def __init__(self):
        self.handlers = []         
    def addHandler(self, handlers): 
        for handler in handlers:
            self.handlers.append(handler)
            handler.setManager(self) 
    def removeHandler(self, handler):
        del self.handlers[self.handlers.index(handler)]
    def update(self):     
        for handler in self.handlers: handler.update()
    def pause(self):
        for handler in self.handlers: handler.pause()
    def stop(self):
        for handler in self.handlers: handler.stop()
    def start(self):    # This is much likely called after the stop() method has been called
        for handler in self.handlers: handler.running = True
    def quit(self): # This terminates the whole program
        pygame.quit()
        raise SystemExit()
            
class Handler:
    """This is the abstract class of all Handlers, all Handlers inherit from this."""
    def __init__(self):
        self.manager    
        self.running
        self.paused
    def update(self):
        pass
    def setManager(self, manager):
        self.manager = manager
    def pause(self, bool):
        self.paused = bool
    def stop(self):
        self.running = False
    def start(self):
        self.running = True

class Input(Handler):
    """This class handles the input. Every tick it gathers all keyboardevents and see if they match with its
        Keyparts. It also creates Inputparts with makepart(), these parts are simply
        containers of Keyparts if we want to have a package of Keyparts for eg. a player"""
    def __init__(self):
        self.parts = []    # These are the containers of Keyparts. They also update the containing parts
        self.keyparts = []    # These parts only handle keyboardpresses, not joystick or mouse
        self.fastkeyparts = [] # Like parts (keyparts) but you can hold down the key
        self.mouseparts = []    # These parts handle mousebuttonpresses
        self.lastKeyPresses = []
    def update(self):
        self.lastKeyPresses = []
        pressed = pygame.key.get_pressed()
        for event in pygame.event.get():    # Get all events currently happening
            if event.type is pygame.KEYDOWN:    # Filter out the keyboardpresses
                self.lastKeyPresses.append(event)
                for part in self.keyparts:    # Loop through all Keyparts
                    if event.key == part.key:    # part.key is an attrubute storing the key the part should react on
                        part.action()    # Now if the parts key IS pressed, then we run its Action()
            if event.type is pygame.MOUSEBUTTONDOWN:    # This is the same as before but for the mouse
                for part in self.mouseparts:
                    if event.button == part.key:    # Note here that we use event.button instead of event.key
                        part.action()
        for part in self.fastkeyparts:
            if pressed[part.key]: part.action()
    # With this we can create an Inputpart object, we send a list of Keyparts to it
    def makePart(self, parts=[None]):    
        part = InputPart()
        self.parts.append(part)
        part.addParts(parts)    # Add the Keyparts to the Inputpart
        part.setHandler(self)
        return part
    # With this method we create the Keyparts for keyboardinput. We send a key (pygame.K), an action (method/function), and a prameter you might want to send (optional)
    def makeKeypart(self, key=pygame.K_e, action=None, parameter=None, fast=False):
        part = KeyPart(key, action, parameter)
        if fast: self.fastkeyparts.append(part) 
        else: self.keyparts.append(part)
        part.setHandler(self)
        return part
    # This is the same as makeKeypart but we create for the mouse. The difference is that the mousekeys is integer values 
    def makeMouseKeypart(self, key=10, action=None, parameter=None):
        part = MouseKeyPart(key, action, parameter)
        self.mouseparts.append(part)
        part.setHandler(self)
        return part
    
class EntityHandler(Handler):
    """The EntityHandler keeps track of all Entities and all Entities thats going to be removed. With 
        makepart() we can create a Entity"""
    def __init__(self):
        self.entityList = []
        self.queue = []   
    def update(self):
        for entity in self.queue: entity.remove()   
        self.queue = []    
        for entity in self.entityList: entity.update()    
    # You can send a list of parts that is going to be added directly, but you can add additional parts later on
    def makeEntity(self, position=(0,0), parts=[None]):    
        entity = Entity(position)
        for part in parts:
            if part is not None: entity.addPart(part) # Here we add all parts
        self.entityList.append(entity)
        entity.setHandler(self)
        return entity
    # With this we can add an Entity to the queue of.. DEATH!! muhaha
    def removeEntity(self, entity):    
        self.queue.append(entity)
        
class Physics(Handler):
    """The PhysicsHandler is the main Handler that keep in touch with chipmunk, the physicsengine.
        This Handler keeps track of all Physicsparts and keep the physicsengine running"""
    def __init__(self, gravity=(0,200)):    
        init_pymunk()    # Initialize the chipmunk engine
        self.parts = []    # All the physicsparts that arent static, these can actually move
        self.staticParts = []    #These parts are stuff like walls
        self.space = Space()
        self.setGravity(gravity)
        self.paused = False    # Its not paused as default
        self.partID = -1    # All Physicsparts get their own ID, this is used in collisiondetection
        self.spaceStep = 1.0/90.0    # This is a constant value, its how long the steps in space should take
        self.space.resize_static_hash()
        self.space.resize_active_hash()
    def update(self):
        for part in self.staticParts: part.update()
        if self.paused is False:
            self.space.step(self.spaceStep)
            #for part in self.parts: part.update() # uncomment this if you have parts without entities
    def setGravity(self, gravity):    
        self.gravity = gravity
        self.space.gravity = Vec2d(self.gravity) # The gravity is defined as a vector

    def makeWallpart(self, verts=[(1,1),(2,2)], mass=1e100, coltype=2, elasticy=2.1):
        part = WallPart(self.space, verts, mass, coltype, elasticy)
        self.staticParts.append(part)
        part.setHandler(self)
        self.partID += 1
        for shape in part.shapes: shape.data = self.partID
        return part
    
    def makeBallpart(self, radius=20, mass=0.5):
        part = BallPart(self.space, radius, mass)
        self.parts.append(part)
        part.setHandler(self)
        self.partID += 1
        part.shape.data = self.partID
        return part
    # With this we can create an collisionaction. Witch is a method that should be run when two specific parts collide
    def addCollisionAction(self, function, CollisionType_a=0, CollisionType_b=0, data=None):
        self.space.add_collisionpair_func(CollisionType_a, CollisionType_b, function, data)
    def pause(self):   
        if self.paused: self.paused = False
        else: self.paused = True
    def removePart(self, shape1, shape2, contact_points, num_contact_points, data):
        for part in self.Physicsparts:
            if part.shape.data == shape2.data: 
                part.parent.handler.removeEntity(part.parent)
        return False

class Renderer(Handler):
    """The RenderHandler handles the rendering of the game sprites, it can create parts with you 
    can for example attach to a entity to make it visible"""
    def __init__(self):
        self.parts = []
        self.shapes = [] 
        self.sprites = pygame.sprite.Group()
        self.bgSurf = pygame.Surface(pygame.display.get_surface().get_size())
        self.originalPic = None # we draw things upon the bgPicture, so we keep the original one so that we can reset it later
    def update(self):
        self.sprites.clear( self.bgSurf, self.bgPic )
        self.sprites.draw( self.bgSurf )
    # with this we can send a filename of a picture
    def makePart(self, filename=None):   
        if filename is None: return self.makeRect()
        part = Renderable(load_image(filename))
        self.addPart(part)
        return part
    # With this method we create a Rectangleshaped sprite with the given color and size=(width, height)
    def makeRect(self, color=[0,0,0], size=(10,10)):
        part = RectPart(color, size)
        self.addPart(part)
        return part
    def makeLine(self, color=[0,0,0], surf=None):
        part = LinePart(color,self.bgPic)
        self.shapes.append(part)
        part.setHandler(self)
        return part
    def makeCircle(self, radian=10, size=0, color=[0,0,0]):
        part = CirclePart(radian, size, color)
        self.addPart(part)
        return part
    def setBgSurface(self, surface): 
        self.bgSurf = surface
        self.blit()
    def setBgPicture(self, surface): 
        self.bgPic = surface
        self.originalPic = pygame.Surface(self.bgPic.get_size())
        self.originalPic.blit(self.bgPic,(0,0))
    def setBgRect(self, rect): 
        self.bgRect = rect
    def addPart(self, part):
        self.parts.append(part)
        self.sprites.add(part)
        part.setHandler(self)
    def blit(self):
        self.bgSurf.blit(self.bgPic, (0,0))
    def cleanBlit(self):
        self.bgPic.blit(self.originalPic, (0,0)) # blit the original background to restore it

class Text(Handler):
    """The TextHandler handles all the Textparts, it updates them and draws them"""
    def __init__(self):
        self.parts = []    # List of all Textparts
        pygame.font.init()    # Initialize the font library
        self.bgSurf = pygame.display.get_surface()    # This is the surface we want to draw our text on
    def update(self):
        for part in self.parts:
            if part.updateAble: # This is used in Menuparts, we sometimes want to have Textparts but dont want to render them at the moment
                part.update()
                part.sprite.draw(self.bgSurf) # All Textparts have their own sprite group called Sprite
    # This method creates a Textpart that takes a method / function as argument. 
    def makeDynamicText(self, data=nothing, position=[0, 0], size=32, color=[255,255,255], antialiasing=True, filename='font.ttf'):
        part = TextPart(data, position, size, color, antialiasing, filename)
        self.parts.append(part)
        part.setHandler(self)
        return part
    # This is just the same as DynamicTextpart but here we send a string instead of a method
    def makeText(self, data='', position=[0, 0], size=32, color=[255,255,255], antialiasing=True, font='font.ttf'):
        part = StaticText(data, position, size, color, antialiasing, font)
        self.parts.append(part)
        return part
                                   
class WindowHandler(Handler):
    """ A simple handler, not very useful yet, can just create a window"""
    def __init__(self):
        self.window = None
    def update(self):
        self.window.update()
    def makeWindow(self, width=800, height=600, flags=0, caption="Untitled", icon=None):
        if not pygame.display.get_init():
           self.window = Window(width, height, flags, caption, icon)
           return self.window
        print "Error: A window has already been initialized"
        return

class MusicHandler(Handler):
    """The MusicHandler is suppose to handle playing music."""
    def __init__(self):
        pygame.mixer.init()
        pygame.mixer.set_reserved(7) # Reserve this channel for playback, so it wont play on any other channel
        self.paused = False  
        self.musicLoaded = False    # Just so music wont be played before we have loaded any music
        self.trackList = []         # List of music to be played
        self.muted = False 
    def update(self):
        if self.musicLoaded and not self.muted and self.running:
            self.trackList[0].play(-1)
    # Add a single track to the playlist
    def addTrack(self, filename, volume=0.6):
        track = load_sound(filename)
        track.set_volume(volume)
        self.trackList.append(track)
        self.musicLoaded = True
    # Add a entire folder to the playlist
    def addFolder(self, folder, extension='.ogg', volume=0.6):
        self.levels = []
        path = os.path.join('data', folder)
        self.folder = folder
        for file in glob.glob( os.path.join(path, '*'+extension)):
            backslash = '|\|'.replace('|', '') # couldnt aquire the backslash character any other way
            name = file.replace('data'+backslash, '') # Remove data from the filename
            name = name.replace('data/', '')
            self.addTrack(name, volume)
    # This cleans the list and add the given track
    def setTrack(self, filename):
        self.trackList = []
        self.addTrack(filename)
    def stop(self):
        if self.musicLoaded: self.trackList[0].stop()
        self.running = False
    def mute(self):
        if self.muted: self.muted = False
        else: 
            self.muted = True
            try: self.trackList[0].stop()
            except IndexError: pass
    def nextTrack(self):
        self.stop()
        try: track = self.trackList.pop(0)
        except IndexError: pass
        self.trackList.append(track)
        self.running = True
    
class BgHandler(Handler):
    """This handles the blitting of the background to the right position"""
    def __init__(self):
        # This just sets some standard values so the game wont crash if you run it before you set them
        self.screen = pygame.display.get_surface() 
        self.bgPic = pygame.Surface((800,600))
        self.bgSurf = self.bgPic
        self.bgRect = pygame.Rect((0,0), (self.screen.get_size()))
    def update(self):
        # This blits the BackgroundSurface on the default surface at the given position
        self.screen.blit(self.bgSurf, (-self.bgRect[0], -self.bgRect[1]))
    def makePart(self, size=None, color=[255,255,255]):
        background = Backgroundpart(size, color)
        return background
    def setBgSurface(self, surface):
        self.bgSurf = surface
    def setBgRect(self, rect):
        self.bgRect = rect
    def setBgPicture(self, pic):
        self.bgPic = pic

class Camera(Handler):
    """ For handling the movement of the screen """
    def __init__(self):
        self.speed = 8  # How fast the screen should scroll
        self.margin = 50      # How far from the screen edge before it moves
        self.center = None      # the center is what the camera follows
        self.resolution = pygame.display.get_surface().get_size()
        self.bgSurf = pygame.display.get_surface()
        self.bgRect = self.bgSurf.get_rect()
        self.mouse = Entity(pygame.mouse.get_pos())
        self.updateMethod = self.freeCamera
    # If we want the camera to follow something
    def followCamera(self):
        self.bgRect.center = (self.center.X, self.center.Y)
    # Freecamera is when you steer the screen with the mouse
    def freeCamera(self):
        self.updateMousePos()
        if self.bgRect.left > 0:
            if self.mouse.X < (self.margin + self.bgRect.left): 
                self.moveLeft()
        if self.bgRect.right < self.bgSurf.get_width():
            if self.mouse.X > (self.resolution[0] - self.margin + self.bgRect.left): 
                self.moveRight()
        if self.bgRect.top > 0:
            if self.mouse.Y < (self.margin + self.bgRect.top):  
                self.moveUp()
        if self.bgRect.bottom < self.bgSurf.get_height():
            if self.mouse.Y > (self.resolution[1] - self.margin + self.bgRect.top): 
                self.moveDown()
    def updateMousePos(self):
        mpos = pygame.mouse.get_pos()
        self.mouse.X = mpos[0]+self.bgRect.left
        self.mouse.Y = mpos[1]+self.bgRect.top
    def moveLeft(self):
        self.bgRect.centerx -= self.speed
    def moveRight(self):
        self.bgRect.centerx += self.speed
    def moveDown(self):
        self.bgRect.centery += self.speed
    def moveUp(self):
        self.bgRect.centery -= self.speed
    def update(self):
        # These if-clauses just prevents the screen from going out of border
        self.updateMethod()
        if self.bgRect.left < 0: self.bgRect.left = 1
        if self.bgRect.top < 0: self.bgRect.top = 1
        if self.bgRect.bottom > self.bgSurf.get_height(): self.bgRect.bottom = self.bgSurf.get_height()-1
        if self.bgRect.right > self.bgSurf.get_width(): self.bgRect.right = self.bgSurf.get_width()-1
        
    def setFollowCamera(self):
        self.bgRect.center = self.center.getPos()
        self.updateMethod = self.followCamera
    def setFreeCamera(self):
        self.updateMethod = self.freeCamera
    def setBgRect(self, rect):
        self.bgRect = rect
    def setBgSurface(self, surface):
        self.bgSurf = surface
    def setBgPicture(self, pic):
        self.bgPic = pic

class WallAdder(Handler):
    """This makes you able to add walls in the game, it draws incomplete walls too"""
    def __init__(self, wallFactory):
        self.points = []
        self.numWalls = 0
        self.wallFactory = wallFactory  # This is a instance of the class WallFactory located in the module factory
        self.wallColor = [0,0,0]
        self.surface = pygame.display.get_surface()
    def update(self):
        if len(self.points) > 0 and len(self.points) < 2:
            endpoint = pygame.mouse.get_pos()
            startpoint = (self.points[-1][0]-self.bgRect.left, self.points[-1][1]-self.bgRect.top)
            pygame.draw.aaline(self.surface, [255,130,10], startpoint, endpoint)
        if len(self.points) > 1: # You cant create walls longer than two points
            self.addWall()
    # Adds the current position of the mouse to the list of points
    def addMousePos(self):
        if len(self.points) < 2:
            point = (pygame.mouse.get_pos()[0]+self.bgRect.left, pygame.mouse.get_pos()[1]+self.bgRect.top)
            self.points.append(point)
    def addWall(self):
        if len(self.points) <= 1: self.points = []
        else:
            self.wallFactory.makeWall(self.points, self.wallColor, True)
            self.numWalls += 1
            self.points = []
    def setBgRect(self, rect):
        self.bgRect = rect
    def setBgSurface(self, surf):
        self.bgSurf = surf
    def setBgPicture(self, pic):
        self.bgPic = pic


class LevelLoader(Handler):
    """This handler can do two things, load a map, or load a folder of maps"""
    def __init__(self, wallFactory, playerFactory): # Very dependent class as it needs two additional handlers
        self.wallFactory = wallFactory 
        self.playerFactory = playerFactory
        self.level = Level()    # A level object, this class is located in the utils module
        self.visibleWalls = True    # If the walls should be visible when they are created
        self.bgRect = pygame.Rect(2,2,2,2)  # Just temp data so the game wont crash
        self.lastLevel = None       # The last level in the list of levels so we know when the game ends
        self.updateList = []        # List of handlers that need to be updated with the new backgrounddata when we load a new level
        self.levels = []            # list of levels (not Level objects but the path to the name in a string)
        self.folder = ''
    # This loads a folder of leves, currently only support loading folders located in the folder maps
    def loadFolder(self, folder):
        self.levels = []
        path = os.path.join('maps', folder)
        self.folder = folder
        for file in glob.glob( os.path.join(path, '*.map')): # This loops through all the map files in the folder and add them to the list
            backslash = '|\|'.replace('|', '')
            name = file.replace('maps'+backslash, '')
            name = name.replace('maps/', '')   
            self.addLevel(name)
        self.lastLevel = self.levels[-1]
        self.loadLevel(self.levels[0]) # It starts the first map in the list
    # This actually loads a map and make it to the currently map we are playing
    def loadLevel(self, filename='empty.map', bgPic=None):
        self.clearLevel()   # Remove old things such as walls and stuff
        file = open(os.path.join('maps', filename), 'rb')
        self.level = pickle.load(file)
        if bgPic is None: bgPic = self.level.bgPicture
        
        self.updateBg(bgPic)
        
        self.wallFactory.physics.setGravity(self.level.gravity)
        # This adds all the objects in the map
        for points in self.level.wallPoints:
            self.wallFactory.makeWall(points, [255,255,255], self.visibleWalls)
        for points in self.level.finishPoints:
            self.wallFactory.makeWall(points, [255,0,254], True, 3)
        for points in self.level.badWallPoints:
            self.wallFactory.makeWall(points, [255,0,0], True, 4)
        for points in self.level.bounceWallPoints:
            self.wallFactory.makeWall(points, [0,0,160], True, 2, 3)
        if self.level.playerStartPos is not None:
            self.playerFactory.makePlayer(self.level.playerStartPos)
            self.bgRect.center = self.level.playerStartPos
        # This prevents us from having a empty file list even if we load a single file
        if len(self.levels) is 0: self.levels.append(filename)
        file.close()
        return self.level
    # This updates the background for a list of handlers when we load a new map
    def updateBg(self, bgPic):
        bgPicture = load_image(bgPic, [255,0,255], os.path.join('maps', self.folder))[0]
        bgSurface = pygame.Surface(bgPicture.get_size())
        self.bgRect = pygame.Rect((0,0), pygame.display.get_surface().get_size())
        
        for handler in self.updateList:
            handler.setBgPicture(bgPicture)
            handler.setBgSurface(bgSurface)
            handler.setBgRect(self.bgRect)

    def addLevel(self, filename):
        self.levels.append(filename)
    # This method simply loops through all the existing objects and deletes them
    def clearLevel(self):
        for wall in self.wallFactory.walls: wall.handler.removeEntity(wall)
        for wall in self.wallFactory.finishList: wall.handler.removeEntity(wall)
        for player in self.playerFactory.players: player.handler.removeEntity(player)
        self.playerFactory.players = [] # Clean the list
        self.wallFactory.walls = []
    def addUpdaters(self, handlers):
        for handler in handlers:
           self.updateList.append(handler)
    # Put the second item in the list first and the first item last
    def nextLevel(self):
        if self.levels[0] is self.lastLevel: return False # It returns false if we have reached the final level
        level = self.levels.pop(0)
        self.levels.append(level)
        self.loadLevel(self.levels[0])
        return True
    def reloadLevel(self):
        self.loadLevel(self.levels[0])
    
class Menu(Handler):
    """This class handles menuparts, it needs a texthandler and a inputhandler. It combines these and
    create a text that respond to input"""
    def __init__(self, text, input):
        self.currentFolder = []
        self.text = text # TextHandler
        self.input = input # InputHandler
        self.mouse = pygame.Rect(2,2,2,2) 
    def update(self):
        self.mouse.topleft = pygame.mouse.get_pos() # update mouse position

        for item in self.currentFolder: # Item is a menupart
            if self.mouse.colliderect(item.textPart.rect): item.setHover(True) # we set the menupart in a hoverstate if the mouse collide with it
            else: item.setHover(False)
            item.update()
    # Here we create a menupart, we can send data about what it should display, what to run when it is activated and how it should activate, styling and such
    def makePart(self, method, data='', position=(50,50), size=52, parameter=None, ActiveColor=[0,0,255], InActiveColor=[255,255,255], font='font.ttf'):
        MenuItem = MenuPart(method, parameter, ActiveColor, InActiveColor)
        MenuItem.setMouseKeypart(self.input.makeMouseKeypart(1, [MenuItem.activate])) # currently, it only supports mouseclicks
        MenuItem.setTextpart(self.text.makeText(data, position, size, [255,255,255], True, font))
        return MenuItem
    # The current folder is simply a list of menuparts
    def setCurrentFolder(self, list):
        for item in self.currentFolder:
             item.textPart.updateAble = False
             item.active = False
        self.currentFolder = list
        for item in self.currentFolder:
            item.textPart.updateAble = True
            
class EnterText(Handler):
    """ a handler witch creates a sort of widget for input of characters, 
    very new class so limited functionality should be expected"""
    def __init__(self, text, input):
        self.text = text # TextHandler
        self.input = input # InputHandler
        self.parts = []
        # charlist is a list of characters that is supported
        self.charlist = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z']
    def update(self):
        key = pygame.key.get_pressed()
        
        for part in self.parts:
            for event in self.input.lastKeyPresses: # lastKeyPresses is just a list of events that recently happend
                keyname = pygame.key.name(event.key) # event.key is a ID, if we send it to key.name() we get the keyname as a string
                try:
                    self.charlist.index(keyname) # If the character exist in our list its okay, else we get a ValueError
                    part.addLetter(keyname)     # Add the letter to the EnterTextPart
                except ValueError: pass
            if key[pygame.K_BACKSPACE]: part.removeLetter()
            if key[pygame.K_SPACE]: part.addLetter(' ')
            part.update()
    def makePart(self, data='', position=[0, 0], size=32, color=[255,255,255], antialiasing=True, font='font.ttf'):
        part = EnterTextPart()
        part.setTextPart(self.text.makeText(data, position, size, color, antialiasing, font))
        self.parts.append(part)
        return part
    
